/*
 * Copyright (c) 2013   Tobias Welther
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package de.tudarmstadt.kom.androidsensorapi.sensormanagement.sensorlistenerholder;

import java.lang.reflect.Method;
import java.util.LinkedList;
import java.util.List;

import de.tudarmstadt.kom.androidsensorapi.sensor.data.AbstractSensorData;
import de.tudarmstadt.kom.androidsensorapi.sensormanagement.sensorinterface.SensorDataListener;

/**
 * Created by Tobias Welther
 */
public class ListenerMethods {

    /**
     * The listener to call the methods on.
     */
    private final SensorDataListener listener;

    /**
     * The methods of the Listener that receive the SensorInterface Updates.
     */
    private final List<Method> eventReceivingMethods;

    /**
     * This is the Class the Data has to be to be sent to the method.
     */
    private final Class<? extends AbstractSensorData> dataWanted;


    /**
     * Creates a list of Methods that SensorInterface Data shall be called upon.
     *
     * @param listener the listener to check for
     * @param dataWanted the class that the data represents
     */
    private ListenerMethods(SensorDataListener listener, Class<? extends AbstractSensorData> dataWanted){
        this.listener = listener;
        this.eventReceivingMethods = new LinkedList<Method>();
        this.dataWanted = dataWanted;
    }


    /**
     * Checks all Methods of the class for the Annotation: {@link SensorData}.
     * If found and the Argument is of type of the {@link SensorListenerHolder}
     * it is registered.
     */
    private void checkMethodsOfClass(){
        for(Method method : listener.getClass().getMethods()){
            method.setAccessible(true);

            SensorData wantedEvent = method.getAnnotation(SensorData.class);
            if(wantedEvent == null) continue;

            if(method.getParameterTypes().length != 1
                    || dataWanted.isAssignableFrom(method.getParameterTypes()[0])){
                //We don't have the Parameter we are looking for.
                continue;
            }

            eventReceivingMethods.add(method);
        }
    }


    /**
     * Spreads the data to the wanted Methods.
     *
     * @param data the data to spread.
     */
    public void callSensorEvent(AbstractSensorData data) {
        for (Method method : eventReceivingMethods){
            try{
                method.invoke(listener, data);
            }catch (Exception exp){
                //Someone has done something wrong.
            }
        }
    }


    /**
     * Checks if the Listener contained is the Listener passed.
     *
     * @param listener to check against.
     * @return true if it is the same, false otherwise.
     */
    public boolean isListener(SensorDataListener listener){
        return this.listener.equals(listener);
    }


    /**
     * Returns if the Listener does not want any Events of the passed Type.
     *
     * @return true if the listener does not want any events, false otherwise.
     */
    public boolean isEmpty(){
        return eventReceivingMethods.isEmpty();
    }


    /**
     * Builds a ListenerMethods with the passed Listener and passed Class to listen.
     *
     * Call {@link #isEmpty()} afterwards to see if the class wants any Data of that type.
     *
     * @param listener to check
     * @param dataClass the data class to check for
     * @return an ListenerMethods containing the methods that want the data.
     */
    public static ListenerMethods generateListenerMethods(SensorDataListener listener,
                                                          Class<? extends AbstractSensorData> dataClass){

        ListenerMethods listenerMethods = new ListenerMethods(listener, dataClass);
        listenerMethods.checkMethodsOfClass();

        return listenerMethods;
    }
}
